package edu.kit.aifb.ldsbench.generator.opt2;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.openrdf.model.Resource;
import org.openrdf.model.Statement;
import org.openrdf.model.URI;
import org.openrdf.model.Value;
import org.openrdf.model.impl.BNodeImpl;
import org.openrdf.model.impl.LiteralImpl;
import org.openrdf.model.impl.NumericLiteralImpl;
import org.openrdf.model.impl.StatementImpl;
import org.openrdf.model.impl.URIImpl;
import org.openrdf.model.vocabulary.RDF;

import edu.kit.aifb.ldsbench.generator.opt.util2.SesameQuery;
import edu.kit.aifb.ldsbench.generator.util2.IDistributionValues;
import edu.kit.aifb.ldsbench.out2.IGeneratorOutput;
import edu.kit.aifb.ldstream.stat.distrib.IDistribution;

class OptimizationEvaluator {

	private SesameQuery sesameQuery;
	private IDistributionValues distribValues;
	private IGeneratorOutput output;

	private int xoptCounter;
	private int linkPositionCounter;
	private int bnodeID;


	OptimizationEvaluator(SesameQuery sesameQuery,
			IDistributionValues distribValues,
			IGeneratorOutput output){
		this.sesameQuery = sesameQuery;
		this.distribValues = distribValues;
		this.output = output;
	}


	void evaluate(OptimizationResult optResult, Map<URI, List<URI>> generationInstances){

		// reset the evaluater instance 
		this.xoptCounter = 0;
		this.linkPositionCounter = 0;
		this.bnodeID = 0;

		double[] xopt = optResult.getXopt();
		ArrayList<Byte> linkageStructure = optResult.getlinkageStructure();
		for(URI type : generationInstances.keySet()){
			for(URI instance : generationInstances.get(type)){
				createTypeDef(instance, type);
				evaluate(type, type, instance.stringValue(), linkageStructure, xopt, this.bnodeID, false);
			}
		}
	}

	private void evaluate(URI type, URI instanceType, String subject, ArrayList<Byte> linkageStructure, 
			double[] xopt, int currentBnodeID, boolean subjectIsBnode){

		Set<URI> currentProperties = this.sesameQuery.getOutgoingProperties(type);
		byte val;
		int skip;

		for(URI property : currentProperties){
			if(this.sesameQuery.typeHasProperty(instanceType, property)){

				val = linkageStructure.get(linkPositionCounter);
				if(val == OptimizationSetter.DIRECT_LINK){

					// we've found a direct linked property add if it's occurence is > 0
					if((int) xopt[this.xoptCounter] > 0){
						handleDirectProperties(instanceType, property, xopt, subject, subjectIsBnode);
					}
					this.xoptCounter++;
					this.linkPositionCounter++;
				}else if(val == OptimizationSetter.UNSET_PROPERTY){
					this.linkPositionCounter++;
				}else if(val == OptimizationSetter.BNODE_START){
					/*
					 * use skip
					 */
					skip = skipEmptyBnodes(linkPositionCounter, linkageStructure);
					if(skip == -1){
						/*
						 * TODO 
						 * we have a bnode non-empty bnode
						 * add the property and crawl further recursively for the next 
						 * 
						 * (see duplicate BNODE_START branch)
						 */

						if(this.sesameQuery.isBNodeProperty(property, instanceType)){
							Resource sub;
							if(subjectIsBnode){
								sub = new BNodeImpl(subject);
							}else{
								sub = new URIImpl(subject);
							}
							Value value = new BNodeImpl("bnode" + currentBnodeID + 1);
							print(sub, property, value);
							//print(subject, addBrackets(property.stringValue()), getBnodeString(currentBnodeID + 1));
						}else{
							handleDirectProperties(instanceType, property, xopt, subject, subjectIsBnode);
							this.xoptCounter++;
						}
						this.linkPositionCounter++;
						// checking the range of the property to gain the linked types
						ArrayList<URI> currentRange = this.sesameQuery.getRangeOfProperty(property) ;

						for(URI range : currentRange){
							// crawl further
							evaluate(range, instanceType, "bnode" + currentBnodeID + 1, 
									linkageStructure, xopt, ++this.bnodeID, true);
							/*
							evaluate(range, instanceType, getBnodeString(currentBnodeID + 1), 
										linkageStructure, xopt, ++this.bnodeID, true);
							 */
						}
						val = linkageStructure.get(linkPositionCounter);
						this.linkPositionCounter++;

					}else{
						// we have an empty bnode
						this.linkPositionCounter += skip;
					}

				}else if(val == OptimizationSetter.BNODE_DELIMITOR){
					this.linkPositionCounter++;
				}
			}else{
				//System.out.print("[unknown : " + property.stringValue() + "] ");
			}
		}

	}

	private int skipEmptyBnodes(int linkPosition, ArrayList<Byte> linkageStructure){

		byte val;
		int bnodeCount = 0;
		int skip = 0;

		for(int i = linkPosition; i < linkageStructure.size() && skip != -1; i++){
			val = linkageStructure.get(i);
			switch(val){
			case OptimizationSetter.DIRECT_LINK: skip = -1; break;
			case OptimizationSetter.BNODE_START: bnodeCount++; skip++; break;
			case OptimizationSetter.BNODE_DELIMITOR: bnodeCount--; skip++; break;
			case OptimizationSetter.UNSET_PROPERTY: skip++; break;
			}
			if(bnodeCount == 0){
				// when the bnodeCount is below zero we reached the end of the bnode
				break;
			}
		}
		return skip;
	}

	private void handleDirectProperties(URI type, URI property, double[] xopt, String subject, boolean subjectIsBnode ){
		IDistribution currentDistrib;
		double[] values;
		String newSubject = subject;

		Resource sub;
		Value obj;

		if(xopt[this.xoptCounter] > 0){
			if(!subjectIsBnode){
				newSubject = subject;//addBrackets(subject);
				sub = new URIImpl(subject);
			}else{
				sub = new BNodeImpl(subject);
			}
			currentDistrib = this.sesameQuery.getDistribution(property, type, SesameQuery.VALUE_DISTRIB);
			values = this.distribValues.getDistributionValue(currentDistrib, (int) xopt[this.xoptCounter]); 

			if(this.sesameQuery.hasMapping(property, type)){
				for(int i = 0; i < xopt[this.xoptCounter]; i++){
					String valueString = this.sesameQuery.getMapping(property, type, (int)values[i]);

					if(valueString.contains("http://")){
						//URI
						obj = new URIImpl(valueString);
					}else{
						//String value
						obj = new LiteralImpl(valueString);
					}
					print(sub, property, obj);		
					/*		
					print(newSubject, addBrackets(property.stringValue()), 
							this.sesameQuery.getMapping(property, type, (int)values[i]));
					 */	
				}
			}else{
				for(int i = 0; i < xopt[this.xoptCounter]; i++){
					obj = new NumericLiteralImpl(values[i]);
					print(sub, property, obj);
					//print(newSubject, addBrackets(property.stringValue()), values[i] +"");
				}
			}
		}
	}

	private void print(Resource subject, URI predicate, Value object){
		Statement s = new StatementImpl(subject, predicate, object);
		this.output.generateOutput(s);
	}


	private void print(String subject, String predicate, String object){
		StringBuilder sb = new StringBuilder(subject);
		sb.append(" ");
		sb.append(predicate);
		sb.append(" ");
		sb.append(object);
		sb.append(" .\n");
		this.output.generateOutput(sb.toString());
	}


	private String getBnodeString(int id){
		return "_bNode" + id;
	}

	private String addBrackets(String s){
		StringBuilder sb = new StringBuilder("<");
		sb.append(s);
		sb.append(">");
		return sb.toString();
	}

	private void createTypeDef(URI instance, URI type){
		Statement sm = new StatementImpl(instance, RDF.TYPE, type);
		this.output.generateOutput(sm);
		/*
		StringBuilder s = new StringBuilder("");
		s.append(instance.stringValue());
		s.append(" <");
		s.append(RDF.TYPE.stringValue());
		s.append("> <");
		s.append(type.stringValue());
		s.append("> . \n");
		this.output.generateOutput(s.toString());
		 */
	}
}
